Skip to content

Content Guidelines: Add AI-powered generate/improve UI via Jetpack#47959

Draft
aagam-shah wants to merge 65 commits intotrunkfrom
add/content-guidelines-plugin-extensibility
Draft

Content Guidelines: Add AI-powered generate/improve UI via Jetpack#47959
aagam-shah wants to merge 65 commits intotrunkfrom
add/content-guidelines-plugin-extensibility

Conversation

@aagam-shah
Copy link
Copy Markdown
Contributor

@aagam-shah aagam-shah commented Apr 6, 2026

Summary

Adds AI-powered guideline generation to the WordPress Content Guidelines admin page via Jetpack. Users can generate guidelines from scratch or improve existing ones, with word-level diffs for review before accepting.

What it does

  • Empty state banner: Rich Jetpack-branded card with "Get started" CTA when all guidelines are empty
  • Header button: "Generate guidelines" / "Improve guidelines" with Jetpack logo, triggers batch generation for all sections
  • Per-section buttons: "Generate guidelines" / "Improve guidelines" tertiary button in each accordion's button row
  • Loading states: Green shimmer animation on textareas + spinner badges in accordion headers during generation
  • Suggestion preview: Word-level diff view (green additions, red strikethrough removals) with "Accept suggestion" / "Dismiss" buttons
  • Availability checks: Handles not-connected, needs-upgrade, and quota-exceeded states

Architecture

Uses DOM injection — Jetpack enqueues a standalone JS+CSS bundle on settings_page_guidelines-wp-admin, uses MutationObserver to inject React components, and shares state via a wp.data store.

Related

Testing instructions

Note: AI suggestions are currently mocked for testing. Flip MOCK to false in _inc/content-guidelines-ai/lib/api.js and remove the return false in lib/availability.js to use the real API.

  1. Build: pnpm jetpack build plugins/jetpack
  2. Enable Gutenberg content-guidelines experiment
  3. Navigate to Settings > Guidelines
  4. Verify: Empty state banner appears with dark gradient background and "Get started" button
  5. Click "Get started" — verify shimmer on textareas, spinners in accordion headers
  6. After generation: verify "Suggestion" badges appear, diff view shows on expand
  7. Click "Accept suggestion" — verify content moves to guideline textarea
  8. Click "Dismiss" — verify suggestion is removed
  9. Navigate to "View history" and back — verify all injections re-appear
  10. With content present: verify header shows "Improve guidelines" and per-section buttons adapt

Does this pull request change what data or activity we track or use?

No. This PR sends site content to the existing Jetpack AI suggest-guidelines endpoint (added in #47479) when the user explicitly clicks a generate button. No new tracking, analytics events, or data collection is introduced.

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack or WordPress.com Site Helper), and enable the add/content-guidelines-plugin-extensibility branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/content-guidelines-plugin-extensibility
bin/jetpack-downloader test jetpack-mu-wpcom-plugin add/content-guidelines-plugin-extensibility

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions bot added [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] In Progress labels Apr 6, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@github-actions github-actions bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Apr 6, 2026
@jp-launch-control
Copy link
Copy Markdown

jp-launch-control bot commented Apr 6, 2026

Code Coverage Summary

Coverage changed in 1 file.

File Coverage Δ% Δ Uncovered
projects/plugins/jetpack/load-jetpack.php 0/47 (0.00%) 0.00% 2 ❤️‍🩹

1 file is newly checked for coverage.

File Coverage
projects/plugins/jetpack/_inc/content-guidelines-ai.php 0/48 (0.00%) 💔

Full summary · PHP report · JS report

If appropriate, add one of these labels to override the failing coverage check: Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage.

@fditrapani
Copy link
Copy Markdown

Thanks for getting this started @aagam-shah. Here are a few comments based on what I see right now:

1. Badges
It looks like we're using custom badges. Can we use these instead: https://wordpress.github.io/gutenberg/?path=/docs/components-badge--docs

2. Suggestions

The suggestions are presented in what appears like a disabled field:
image

The container should look and behave like a regular input (but have a green border). We need to match the height of the input (and visa versa) to prevent layout shift. We also can accept a suggestion if someone clicks on the suggestion to start editing it. When the section is open, we don't need to show the suggestion badge any longer since the input + buttons communicate the suggestion.

This is what I had in the design:

image

3. Banner behaviour

The main banner dismisses if you generate a suggestion from within a section. The causes the layout to shift and feels jarring. The banner should only dismiss if the user dismisses it or the main call to action is used to generate guidelines for all the sections.

4. Block suggestions

I'm not seeing any block suggestions being added. Are you planning on adding those separately? We should have suggestions for the block section as well as the blocks themselves.

image image image image image image

@aagam-shah aagam-shah force-pushed the add/content-guidelines-plugin-extensibility branch from dfbee81 to 3c42428 Compare April 8, 2026 08:46
@aagam-shah
Copy link
Copy Markdown
Contributor Author

Thanks for the detailed feedback @fditrapani! Here's where we're at on each point:

1. Badges
I have am using Jetpack's Badge component because using the Gutenberg Badge from @wordpress/ui would require adding a new dependency to the Jetpack plugin just for this. Happy to revisit if you feel strongly about using the Gutenberg one — it would need @wordpress/ui added to package.json.

2. Suggestions
Addressed — the suggestion container now matches the Gutenberg textarea styling (same padding, border-radius, 1px green border, dynamic height to prevent layout shift). Clicking anywhere on the diff accepts the suggestion. The badge also hides when the accordion is expanded since the Accept/Dismiss buttons already communicate the state.

3. Banner behaviour
Fixed — the banner now only dismisses when the user explicitly clicks "Get started" or "Close" / X. Per-section generation no longer affects it. Dismiss state is persisted via localStorage so it survives page reloads.

4. Block suggestions
Not included in this PR — planning to add those as a follow-up. The backend already supports the 4 main sections (site, copy, images, additional) but block-level generation will need additional work on both the API and UI side.

@fditrapani
Copy link
Copy Markdown

Thanks for the updates @aagam-shah!

I have am using Jetpack's Badge component because using the Gutenberg Badge from @wordpress/ui would require adding a new dependency to the Jetpack plugin just for this. Happy to revisit if you feel strongly about using the Gutenberg one — it would need @wordpress/ui added to package.json.

Sounds good to me if this is the badge we're already using. I understand we're updating components but hopefully this will get updates along with the others when that happens.

Addressed — the suggestion container now matches the Gutenberg textarea styling (same padding, border-radius, 1px green border, dynamic height to prevent layout shift). Clicking anywhere on the diff accepts the suggestion. The badge also hides when the accordion is expanded since the Accept/Dismiss buttons already communicate the state.

Thanks for the updates. This is looking better. We still have some shifting happening if the text area isn't the default size:

Screen.Recording.2026-04-08.at.12.51.15.PM.mov

Fixed — the banner now only dismisses when the user explicitly clicks "Get started" or "Close" / X. Per-section generation no longer affects it. Dismiss state is persisted via localStorage so it survives page reloads.

Nice! looking better here too. I noticed a bug where the banner unexpectedly goes away if you accept a suggestion.

Screen.Recording.2026-04-08.at.12.53.02.PM.mov

Jetpack logo

I got some feedback from Devin that the logo should be reversed instead of the regular colour. (p1775665181650579/1775564866.152469-slack-C08NFD7RA58)

Can we update the logo to look like this:

image

@aagam-shah aagam-shah force-pushed the add/content-guidelines-plugin-extensibility branch 2 times, most recently from 0b4325a to e03390e Compare April 8, 2026 20:05
@aagam-shah
Copy link
Copy Markdown
Contributor Author

Thanks @fditrapani! Here's an update on the latest changes:

1. Suggestions UX — Updated:

  • Diff container now matches the Gutenberg textarea styling (1px green border, same padding/border-radius, white background)
  • Clicking anywhere on the diff accepts the suggestion
  • Badge hides when accordion is expanded
  • Height captured from textarea before hiding to prevent layout shift
  • Diff container is also resizable

2. Banner behaviour — Fixed:

  • Banner only dismisses on "Get started", "Close", or X button click
  • Per-section generation no longer hides the banner
  • Dismiss state persisted via wp.data store + localStorage
  • Header button hidden while banner is visible

3. Block suggestions — Still planned as a follow-up.

4. Upgrade notice — Added a persistent warning Notice (instead of snackbar) with a "View plans" button when AI is unavailable. Matches the standard WordPress notice pattern.

aagam-shah and others added 17 commits April 9, 2026 11:11
Injects an AI-powered "Generate with Jetpack" button next to each
"Save guidelines" button on the Content Guidelines settings page.
Uses DOM injection via MutationObserver to find accordion forms and
render React buttons that call the suggest-guidelines API endpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass AI availability, connection status, and upgrade URL from PHP to JS
- Show contextual snackbar with action link when AI is unavailable:
  - Not connected: "Connect" action linking to Jetpack dashboard
  - Connected but no AI: "Upgrade" action linking to checkout
  - wpcom vs self-hosted sites get appropriate upgrade URLs
- Disconnect MutationObserver once all buttons are injected
- Inline buttons next to Save guidelines in a flex row

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Content Guidelines page is registered with slug 'guidelines-wp-admin',
not 'content-guidelines-wp-admin'. Without this fix the script never enqueues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split monolithic 146-line JS file into:
- constants.js (store name, sections, API path)
- lib/api.js (suggest-guidelines API wrapper)
- lib/inject.js (DOM injection + MutationObserver)
- components/generate-button.jsx (per-section button)

Entry point is now a thin import + init. Button label changes
to "Improve with Jetpack" when content exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New SuggestAllButton component generates all 4 guideline sections
at once. Injected into the .admin-ui-page__header via DOM injection.
Label adapts: "Generate guidelines" when empty, "Improve guidelines"
when content exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows a Jetpack-branded card above the guideline list when all
sections are empty, encouraging users to generate their first set
of guidelines. Hides automatically when content exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Target .admin-ui-page__header-actions instead of .admin-ui-page__header
so the button renders in the header row next to the title, not below
the subtitle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Option B design only has the top-level "Generate/Improve guidelines"
button and the empty state banner. Per-section buttons are not part
of the design.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add wp.data store (jetpack/content-guidelines-ai) for shared
  suggestion state across injected React roots
- Show "Suggestion" badge in accordion headers when a suggestion exists
- Show loading spinner in accordion headers while generating
- Show suggestion preview with Accept/Dismiss inside expanded accordions
- Accept writes to core/content-guidelines store, Dismiss discards
- Header button now writes to suggestion store instead of directly
  to the guideline store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add per-section "Improve/Generate guidelines" tertiary button in each
  accordion's button row (triggers single-section AI generation)
- When suggestion exists: hide Gutenberg's DataForm + Save/Clear via
  CSS class toggle, show suggestion textarea + Accept/Dismiss instead
- Add Jetpack icon SVG to the header "Improve guidelines" button
- Add per-section loading state to the store so each accordion shows
  its own spinner independently
- Update suggestion badge to use per-section loading

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes:
- Extract shared availability check to lib/availability.js
- Centralize config in constants.js
- MutationObserver disconnects once all injections complete
- Remove unused store actions/selectors
- Add useEffect cleanup for DOM class manipulation
- Use Jetpack CSS custom properties for colors

Banner upgrade:
- Rich dark gradient background with green orb decorations
- Matches exploration-b prototype design
- Shows when all guidelines empty, hides automatically
- No localStorage persistence — purely data-driven

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JetpackLogo component has transparent paths that let the blue
button background bleed through. Use inline SVG with explicit
white fill for the lightning bolt instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
aagam-shah and others added 19 commits April 9, 2026 11:11
The overlay approach introduced DOM reparenting of React-managed
elements which is an anti-pattern. Revert to the simpler approach
of hiding Gutenberg's form and showing our diff container.

Improved selectors: target .dataforms-layouts__wrapper and
.components-h-stack by known class names instead of the fragile
:first-child > :not(...) structural selector.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 1px green border (no box-shadow), 2px border-radius, 9px 11px padding
- Dynamic height via CSS variable read from textarea before hiding
- Prevents layout shift when toggling between textarea and diff view

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move bannerDismissed to the AI store instead of cross-reading
localStorage between separate React roots. Both components now
subscribe to the same store selector. localStorage is still used
for persistence across page loads, but only read on store init
and written in the dismissBanner action.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Banner visibility now based solely on bannerDismissed store state,
not on whether guidelines exist. Prevents banner from hiding when
a suggestion is accepted (which writes to the guideline store).

Jetpack logo reversed to white circle with green lightning for
better contrast on the primary button, per design feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Triangles sit on top of the white circle in the SVG stacking
order, so transparent shows white not the button. Use the
WP components accent color variable to match the button bg.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This component uses a single SVG path with cutout triangles,
so the button background naturally shows through the lightning
bolt. No CSS overrides needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Read the textarea's current value from the DOM before hiding it,
so the diff reflects unsaved edits the user may have made.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sync textarea height with the captured CSS variable so accepting
a suggestion doesn't reset the textarea to its default height.
The variable persists on the form after accept, keeping both
the diff container and textarea at the same height.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drop the fragile CSS variable height matching. Both the diff
container and textarea now size naturally to their content.
Diff container scrolls if content overflows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use ResizeObserver to continuously track the textarea's height
in a ref while it's visible. When the suggestion appears, the
diff container uses the captured height as inline style. This
handles user-resized textareas without timing issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of tracking textarea height in JS, make the diff
container resizable like the textarea. Both elements size
to their content naturally and the user can resize either.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Read offsetHeight once when suggestion first arrives (textarea
still visible), store in state so it triggers a re-render with
the correct inline height on the diff container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API now uses categories-based format:
  Request:  { categories: { site: {}, copy: { guidelines: "..." } } }
  Response: { site: { guidelines: "..." }, copy: { guidelines: "..." } }

Translation is contained in api.js — callers unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show a warning Notice with "View plans" button instead of a
transient snackbar when Jetpack AI is unavailable. The notice
is injected above the guideline list, dismissible, and managed
via the AI store.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add box-sizing: border-box so the Notice's border and padding
are included in the 680px width, aligning with the accordions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aagam-shah aagam-shah force-pushed the add/content-guidelines-plugin-extensibility branch from b68d8ed to 9b3b41f Compare April 9, 2026 06:15
@fditrapani
Copy link
Copy Markdown

Nice work @aagam-shah. This is looking great. One really minor thing! When you hover over a suggestion field, we show the pointer cursor. It's not technically wrong but the text cursor does a better job at indicating that it's editable.

aagam-shah and others added 8 commits April 9, 2026 19:49
Mirrors SectionGenerateButton for the block guideline editing modal.
Reads textarea value from DOM, calls suggestGuidelines API, and stores
the suggestion in Redux using the block name as the store key.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add block-level injection points in inject.js for the block guideline
modal. Imports BlockGenerateButton and BlockSuggestionActions components,
registers their slots, adds getBlockNameFromModal helper to extract the
block name from modal inputs, and injects both components into the modal
when it is open.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add "Improve guidelines" / "Generate guidelines" AI feature to the block
guideline editing modal, mirroring the existing section-level feature.

- BlockSuggestionActions: diff view with shimmer and class toggling
- BlockSuggestionButtons: Improve/Accept/Dismiss buttons row
- DiffView: shared diff rendering component (used by sections and blocks)
- lib/dom.js: shared setTextareaValue and acceptBlockSuggestion helpers
- Block-specific shimmer CSS applied directly on textarea background
- Clears stale suggestions on modal close

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aagam-shah
Copy link
Copy Markdown
Contributor Author

Hey @fditrapani , I've added the Improve/Generate guidelines flow to the block edit modal — shimmer loading, word-level diff, accept/dismiss all working.

For showing suggestions in the block list (DataViews table), the injection is getting complex since DataViews doesn't expose easy extension points and the row/action structure is harder to hook into compared to the modal. So for now suggestions are scoped to the edit modal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] In Progress [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants